﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using System.IO;

namespace Hack.CompilerServices
{
    public interface ISourceFile
    {
        SourceFileEntry Entry
        {
            get;
        }
    }

    public interface ISourceMethod
    {
        string Name
        {
            get;
        }

        int NamespaceID
        {
            get;
        }

        int Token
        {
            get;
        }
    }

    public class MonoSymbolWriter
    {
        protected ArrayList locals = null;
        protected ArrayList methods = null;
        protected ArrayList sources = null;
        protected readonly MonoSymbolFile file;
        private string filename = null;

        LineNumberEntry[] current_method_lines;
        int current_method_lines_pos = 0;

        internal ISourceFile[] Sources
        {
            get
            {
                ISourceFile[] retval = new ISourceFile[sources.Count];
                sources.CopyTo(retval, 0);
                return retval;
            }
        }

        private SourceMethod current_method = null;

        public MonoSymbolWriter(string filename)
        {
            this.methods = new ArrayList();
            this.sources = new ArrayList();
            this.locals = new ArrayList();
            this.file = new MonoSymbolFile();

            this.filename = filename + ".mdb";

            this.current_method_lines = new LineNumberEntry[50];
        }

        public void CloseNamespace()
        { }

        public void DefineLocalVariable(int index, string name, byte[] signature)
        {
            if (current_method == null)
                return;

            current_method.AddLocal(index, name, signature);
        }

        public void MarkSequencePoint(int offset, int line, int column)
        {
            if (current_method == null)
                return;

            if (current_method_lines_pos == current_method_lines.Length)
            {
                LineNumberEntry[] tmp = current_method_lines;
                current_method_lines = new LineNumberEntry[current_method_lines.Length * 2];
                Array.Copy(tmp, current_method_lines, current_method_lines_pos);
            }

            current_method_lines[current_method_lines_pos++] = new LineNumberEntry(line, offset);
        }

        public void OpenMethod(ISourceFile file, ISourceMethod method,
                    int startRow, int startColumn,
                    int endRow, int endColumn)
        {
            SourceMethod source = new SourceMethod(
                file, method, startRow, startColumn, endRow, endColumn);

            current_method = source;
            methods.Add(current_method);
        }

        public void CloseMethod()
        {
            current_method.SetLineNumbers(
                current_method_lines, current_method_lines_pos);
            current_method_lines_pos = 0;

            current_method = null;
        }

        public SourceFileEntry DefineDocument(string url)
        {
            SourceFileEntry entry = new SourceFileEntry(file, url);
            sources.Add(entry);
            return entry;
        }

        public int DefineNamespace(string name, SourceFileEntry source,
                        string[] using_clauses, int parent)
        {
            if ((source == null) || (using_clauses == null))
                throw new NullReferenceException();

            return source.DefineNamespace(name, using_clauses, parent);
        }

        public int OpenScope(int startOffset)
        {
            if (current_method == null)
                return 0;

            current_method.StartBlock(startOffset);
            return 0;
        }

        public void CloseScope(int endOffset)
        {
            if (current_method == null)
                return;

            current_method.EndBlock(endOffset);
        }

        public void WriteSymbolFile(Guid guid)
        {
            foreach (SourceMethod method in methods)
            {
                method.SourceFile.Entry.DefineMethod(
                    method.Method.Name, method.Method.Token,
                    method.Locals, method.Lines, method.Blocks,
                    method.Start.Row, method.End.Row,
                    method.Method.NamespaceID);
            }

            try
            {
                // We mmap the file, so unlink the previous version since it may be in use
                File.Delete(filename);
            }
            catch
            {
                // We can safely ignore
            }
            using (FileStream fs = new FileStream(filename, FileMode.Create, FileAccess.Write))
            {
                file.CreateSymbolFile(guid, fs);
            }
        }

        protected class SourceMethod
        {
            LineNumberEntry[] lines;
            private ArrayList _locals;
            private ArrayList _blocks;
            private Stack _block_stack;
            private int next_block_id = 0;
            private ISourceMethod _method;
            private ISourceFile _file;
            private LineNumberEntry _start, _end;

            private LexicalBlockEntry _implicit_block;

            public SourceMethod(ISourceFile file, ISourceMethod method,
                         int startLine, int startColumn,
                         int endLine, int endColumn)
            {
                this._file = file;
                this._method = method;

                this._start = new LineNumberEntry(startLine, 0);
                this._end = new LineNumberEntry(endLine, 0);

                this._implicit_block = new LexicalBlockEntry(0, 0);
            }

            public void StartBlock(int startOffset)
            {
                LexicalBlockEntry block = new LexicalBlockEntry(
                    ++next_block_id, startOffset);
                if (_block_stack == null)
                    _block_stack = new Stack();
                _block_stack.Push(block);
                if (_blocks == null)
                    _blocks = new ArrayList();
                _blocks.Add(block);
            }

            public void EndBlock(int endOffset)
            {
                LexicalBlockEntry block =
                    (LexicalBlockEntry)_block_stack.Pop();

                block.Close(endOffset);
            }

            public LexicalBlockEntry[] Blocks
            {
                get
                {
                    if (_blocks == null)
                        return new LexicalBlockEntry[0];
                    else
                    {
                        LexicalBlockEntry[] retval =
                            new LexicalBlockEntry[_blocks.Count];
                        _blocks.CopyTo(retval, 0);
                        return retval;
                    }
                }
            }

            public LexicalBlockEntry CurrentBlock
            {
                get
                {
                    if ((_block_stack != null) && (_block_stack.Count > 0))
                        return (LexicalBlockEntry)_block_stack.Peek();
                    else
                        return _implicit_block;
                }
            }

            public LineNumberEntry[] Lines
            {
                get
                {
                    return lines;
                }
            }

            public LocalVariableEntry[] Locals
            {
                get
                {
                    if (_locals == null)
                        return new LocalVariableEntry[0];
                    else
                    {
                        LocalVariableEntry[] retval =
                            new LocalVariableEntry[_locals.Count];
                        _locals.CopyTo(retval, 0);
                        return retval;
                    }
                }
            }

            public void AddLocal(int index, string name, byte[] signature)
            {
                if (_locals == null)
                    _locals = new ArrayList();
                _locals.Add(new LocalVariableEntry(
                             index, name, signature, CurrentBlock.Index));
            }

            public ISourceFile SourceFile
            {
                get { return _file; }
            }

            public ISourceMethod Method
            {
                get { return _method; }
            }

            public LineNumberEntry Start
            {
                get { return _start; }
            }

            public LineNumberEntry End
            {
                get { return _end; }
            }

            //
            // Passes on the lines from the MonoSymbolWriter. This method is
            // free to mutate the lns array, and it does.
            //
            internal void SetLineNumbers(LineNumberEntry[] lns, int count)
            {
                int pos = 0;

                int last_offset = -1;
                int last_row = -1;
                for (int i = 0; i < count; i++)
                {
                    LineNumberEntry line = lns[i];

                    if (line.Offset > last_offset)
                    {
                        if (last_row >= 0)
                            lns[pos++] = new LineNumberEntry(
                                last_row, last_offset);

                        last_row = line.Row;
                        last_offset = line.Offset;
                    }
                    else if (line.Row > last_row)
                    {
                        last_row = line.Row;
                    }
                }

                lines = new LineNumberEntry[count + ((last_row >= 0) ? 1 : 0)];
                Array.Copy(lns, lines, pos);
                if (last_row >= 0)
                    lines[pos] = new LineNumberEntry(
                        last_row, last_offset);
            }
        }
    }
}
